#include "asan_report.h"

#include <execinfo.h>
#include <stdio.h>

#include "asan_alloctor.h"
#include "asan_interfaces_internal.h"
#include "asan_internal.h"
#include "asan_internal_defs.h"
#include "asan_mapping.h"
#include "asan_shadow_memory.h"
#include "asan_signal_handler.h"
#include "asan_thread.h"
#include "asan_utils.h"
#include "common_flags.h"
#include "sanitizer_printf.h"
#include "sanitizer_libc.h"

namespace __sanitizer {

static const int PRINT_BUFF_SIZE = 4096 * 4;
static const char *line_spliter = "=================================================================\n";

ASAN_REPORT_ERROR(load, false, 1)
ASAN_REPORT_ERROR(load, false, 2)
ASAN_REPORT_ERROR(load, false, 4)
ASAN_REPORT_ERROR(load, false, 8)
ASAN_REPORT_ERROR(load, false, 16)
ASAN_REPORT_ERROR_N(load, true)

ASAN_REPORT_ERROR(store, true, 1)
ASAN_REPORT_ERROR(store, true, 2)
ASAN_REPORT_ERROR(store, true, 4)
ASAN_REPORT_ERROR(store, true, 8)
ASAN_REPORT_ERROR(store, true, 16)
ASAN_REPORT_ERROR_N(store, true)

static bool SuppressErrorReport(uptr pc) {
  if (!common_flags()->suppress_equal_pcs) return false;
  return false;
}

void PrintShadowErrorTable(uptr addr) {
  PrintfDecorator d;
  uptr saddr = MEM_TO_SHADOW(addr);
  int row_bytes = 16;
  uptr print_beg_saddr = RoundDownTo(saddr, row_bytes) - row_bytes * 4;
  uptr print_end_saddr = RoundUpTo(saddr, row_bytes) + row_bytes * 4 - 1;
  print_beg_saddr = Max<uptr>(kLowShadowBeg, print_beg_saddr);
  print_end_saddr = Min<uptr>(kHighShadowEnd, print_end_saddr);

  char table_buff[PRINT_BUFF_SIZE];
  internal_memset(table_buff, 0, PRINT_BUFF_SIZE);
  int buff_idx = 0;
  for (uptr beg = print_beg_saddr; beg <= print_end_saddr; beg += row_bytes) {
    const char *row_prefix = (saddr >= beg && saddr < beg + row_bytes) ? "=>" : "  ";
    buff_idx += REAL(snprintf(table_buff + buff_idx, PRINT_BUFF_SIZE - buff_idx, "%s%p: ", row_prefix, (void *)beg));
    for (uptr offset = 0; offset < row_bytes; ++offset) {
      su_t *to_print_saddr = (su_t *)(beg + offset);
      if (beg + offset == saddr) {
        buff_idx +=
            REAL(snprintf(table_buff + buff_idx, PRINT_BUFF_SIZE - buff_idx, "[%s]", d.GetShadowByteStr(*to_print_saddr)));
      } else if (beg + offset <= print_end_saddr) {
        buff_idx +=
            REAL(snprintf(table_buff + buff_idx, PRINT_BUFF_SIZE - buff_idx, " %s ", d.GetShadowByteStr(*to_print_saddr)));
      }
    }
    table_buff[buff_idx] = '\n';
    buff_idx += 1;
  }
  table_buff[buff_idx] = '\0';
  table_buff[PRINT_BUFF_SIZE - 1] = '\0';
  Printf(table_buff);
}

void PrintMagicValueRoutine() {
  PrintfDecorator d;
  char buff[PRINT_BUFF_SIZE];
  internal_memset(buff, 0, PRINT_BUFF_SIZE);
  int buff_idx = 0;
  buff_idx += REAL(snprintf(buff + buff_idx, PRINT_BUFF_SIZE - 1,
      "Shadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:             "
      "00\n  Partially addressable:   01 02 03 04 05 06 07\n"));
  #define __ASAN_SNP(format, magic) \
  buff_idx += REAL(snprintf(buff+buff_idx, PRINT_BUFF_SIZE-buff_idx-1,\
      (format), d.GetShadowByteStr((magic))));
  __ASAN_SNP("  Heap left redzone:       %s\n", kAsanHeapLeftRedzoneMagic);
  __ASAN_SNP("  Freed heap region:       %s\n", kAsanHeapFreeMagic);
  __ASAN_SNP("  Stack left redzone:      %s\n", kAsanStackLeftRedzoneMagic);
  __ASAN_SNP("  Stack mid redzone:       %s\n", kAsanStackMidRedzoneMagic);
  __ASAN_SNP("  Stack right redzone:     %s\n", kAsanStackRightRedzoneMagic);
  __ASAN_SNP("  Stack after return:      %s\n", kAsanStackAfterReturnMagic);
  __ASAN_SNP("  Stack use after scope:   %s\n", kAsanStackUseAfterScopeMagic);
  __ASAN_SNP("  Global redzone:          %s\n", kAsanGlobalRedzoneMagic);
  __ASAN_SNP("  Global init order:       %s\n", kAsanInitializationOrderMagic);
  __ASAN_SNP("  Poisoned by user:        %s\n", kAsanUserPoisonedMemoryMagic);
  __ASAN_SNP("  Container overflow:      %s\n", kAsanContiguousContainerOOBMagic);
  __ASAN_SNP("  Array cookie:            %s\n", kAsanArrayCookieMagic);
  __ASAN_SNP("  Intra object redzone:    %s\n", kAsanIntraObjectRedzone);
  #undef __ASAN_SNP
  buff[buff_idx] = '\0';
  Printf(buff);
}

static bool in_reporting = false;
void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write, uptr access_size, u32 exp, bool fatal) {
  if (in_reporting) return;
  in_reporting = true;
  if (__asan_test_only_reported_buggy_pointer) {
    *__asan_test_only_reported_buggy_pointer = addr;
    return;
  }
  if (!fatal && SuppressErrorReport(pc)) return;
  ENABLE_FRAME_POINTER;

  // Optimization experiments.
  // The experiments can be used to evaluate potential optimizations that remove
  // instrumentation (assess false negatives). Instead of completely removing
  // some instrumentation, compiler can emit special calls into runtime
  // (e.g. __asan_report_exp_load1 instead of __asan_report_load1) and pass
  // mask of experiments (exp).
  // The reaction to a non-zero value of exp is to be defined.
  (void)exp;
  const char *behavior = is_write ? "WRITE" : "READ";
  ReportStackTrace();
  ReportErrorInfo(pc, bp, sp, addr, behavior, access_size);
  PrintShadowErrorTable(addr);
  PrintMagicValueRoutine();
  Die();
}

void ReportDoubleFree(uptr pc, uptr bp, uptr sp, uptr addr) {
  const char *behavior = "double-free";
  ReportStackTrace();
  ReportErrorInfo(pc, bp, sp, addr, behavior, 0);
  PrintShadowErrorTable(addr);
  PrintMagicValueRoutine();
  Die();
}

void ReportFreeNotMalloced(uptr pc, uptr bp, uptr sp, uptr addr) {
  const char *behavior = "free-not-malloced";
  ReportStackTrace();
  ReportErrorInfo(pc, bp, sp, addr, behavior, 0);
  PrintShadowErrorTable(addr);
  PrintMagicValueRoutine();
  Die();
}

void CheckAndReport(const void *membeg, uptr size, bool is_write) {
  if (!asan_inited) return;
  if (allocatorPtr->shadow->IsPoisonedMem(membeg, size)) {
    uptr err_addr = allocatorPtr->shadow->GetPoisonedAddr(membeg, size);
    GET_CALLER_PC_BP_SP;
    ReportGenericError(pc, bp, sp, err_addr, is_write, size, 0, is_write);
  }
}

void ReportErrorInfo(uptr pc, uptr bp, uptr sp, uptr addr, const char *behavior, uptr access_size) {
  PrintfDecorator d;
  Pid pid = GetCurrentPid();
  Tid tid = GetCurrentTidOrInvalid();
  char buff[PRINT_BUFF_SIZE];
  // do not initialize with = {0}, this will call __interceptor_memset
  internal_memset(buff, 0, PRINT_BUFF_SIZE);
  int buff_idx = 0;
  buff_idx += REAL(snprintf(buff + buff_idx, PRINT_BUFF_SIZE - 1,
                       "%s%s==%u==ERROR: AddressSanitizer: %s on address %p at pc %p bp %p sp %p%s\n", line_spliter,
                       d.Error(), pid, behavior, (void *)addr, (void *)pc, (void *)bp, (void *)sp, d.Default()));
  buff_idx += REAL(snprintf(buff + buff_idx, PRINT_BUFF_SIZE - buff_idx - 1, "%s%s of size %lu at %p thread T%u%s\n",
                       d.Blue(), behavior, access_size, (void *)addr, tid, d.Default()));
  buff[buff_idx] = '\0';
  Printf(buff);
}

static bool in_reporting_stack = false;
void ReportStackTrace() {
  // We may get error in this function,
  // avoid recursive call in here
  if (in_reporting_stack) return;
  in_reporting_stack = true;
  static uptr stack_depth = 128;
  void *callstack[stack_depth];
  size_t frames = backtrace(callstack, stack_depth);
  char **symbols = backtrace_symbols(callstack, frames);
  for (size_t i = 0; i < frames; i++) {
    if (!symbols)
      Printf("  #%d %p\n", i, callstack[i]);
    else
      Printf("  #%d %s\n", i, symbols[i]);
  }
  Printf("\n");
  REAL(free(symbols));
  // report once, never reset to false
  // in_reporting_stack = false;
}

void ReportCapturedDeadlySignal(int signal, uptr pc, uptr bp, uptr sp, SignalContext sc) {
  VReport(ASAN_LOG_DEBUG, "%s:%d %s\n", __FILE__, __LINE__, __func__);
  PrintfDecorator d;
  Pid pid = GetCurrentPid();
  Tid tid = GetCurrentTidOrInvalid();
  char *buff = (char *)REAL(malloc(PRINT_BUFF_SIZE * sizeof(char)));
  int buff_idx = 0;
  buff_idx +=
      REAL(snprintf(buff + buff_idx, PRINT_BUFF_SIZE - buff_idx - 1,
               "%s%s==%u==ERROR: AddressSanitizer: %s(%d) on unknown address (pc %p bp %p sp %p T%u)\n%s", line_spliter,
               d.Error(), pid, GetSignalStr(signal), signal, (void *)pc, (void *)bp, (void *)sp, tid, d.Default()));
  if (sc.is_memory_access) {
    const char *behavior = (sc.write_flag == SignalContext::WriteFlag::Write) ? "WRITE" : "INVALID";
    buff_idx += REAL(snprintf(buff + buff_idx, PRINT_BUFF_SIZE - buff_idx - 1,
                         "==%u==The signal is caused by a %s memory access.\n", pid, behavior));
  }
  buff[buff_idx] = '\0';
  Printf(buff);
  REAL(free(buff));
  ReportStackTrace();
  Die();
}

}  // namespace __sanitizer
